#include "common/assert.h"
#include "common/exception.h"
#include "win32/throw_win32_exception.h"
#include "io/file_stream.h"

// XXX - this is supposed to be platform-independent.
// Use io_Exceptions instead of Win32 ones?

// Open a file for reading and writing. 'filename' is discarded after
// file is opened.
File_Stream::File_Stream(const char* filename)
{
	m_file = CreateFile(
		filename,
		FILE_READ_DATA | FILE_WRITE_DATA,
		FILE_SHARE_WRITE,
		0,
		OPEN_ALWAYS,
		0,
		0);

	if (m_file == INVALID_HANDLE_VALUE)
		Throw_Win32_Exception("CreateFile");
}

// Read at most 'count' elements into buf. Return number of actual
// bytes read. Advance pos().
int64 File_Stream::read(char* buf, int count)
{
	DWORD bytes_read;
	
	BOOL ok = ReadFile(
		m_file,
		(void*) buf,
		count,
		&bytes_read,
		0);

	if (ok == 0)
		Throw_Win32_Exception("ReadFile");

	return bytes_read;
}

// Write 'count' elements. Advance pos() if applicable.
void File_Stream::write(const char* buf, int64 count)
{
	ASSERT(count < 0x7fffffff);

	DWORD bytes_written;

	BOOL ok = WriteFile(
		m_file,
		(const void*) buf,
		(int32) count,
		&bytes_written,
		0);

	if (ok == 0)
		Throw_Win32_Exception("WriteFile");

	// Written bytes can be < count only with nonblocking pipes
	// with insufficient buffer space
	ASSERT(bytes_written == count);

	return;
}

// Helper function used by File_Stream::seek(int64) and
// File_Stream::length(int64). Does not check that 'pos' is a valid
// value for a file pointer.
static int64 internal_seek(HANDLE file, int64 pos, DWORD move_method)
{
	LONG pos_high = (int32) ((pos & 0xffffffff00000000LL) >> 32);
	DWORD new_pos_low = SetFilePointer(
		file,
		(int32) (pos & 0xffffffffLL),
		&pos_high,
		move_method);

	// XXX: Must use GetLastError since INVALID_SET_FILE_POINTER
	// is a valid result value for SetFilePointer().
	// However, GetLastError() sometimes returns an error even
	// though there was none. Therefore, we cannot check errors.

	//DWORD actual_result = GetLastError();
	//if (actual_result != NO_ERROR)
	//	Throw_Win32_Exception("SetFilePointer");

	return (((int64) pos_high) << 32) | ((int64) new_pos_low);
}

// Seek to given length.
// pre(0 <= pos && pos <= length());
// post(pos() == pos);
void File_Stream::seek(int64 pos)
{
	// Don't check length because it uses pos()
	ASSERT(0 <= pos/* && pos <= length()*/);

	internal_seek(m_file, pos, FILE_BEGIN);

	ASSERT(this->pos() == pos);
}

// Position
// post(0 <= pos() && pos() <= length());
int64 File_Stream::pos()
{
	int64 result = internal_seek(m_file, 0, FILE_CURRENT);

	// Don't check length because it uses pos()
	ASSERT(0 <= result /* && result <= length() */ );
	return result;
}

// Total length
// post(length() >= 0);
int64 File_Stream::length()
{
	// There must be a better way
	int64 old_pos = pos();
	int64 result = internal_seek(m_file, 0, FILE_END);
	seek(old_pos);

	ASSERT(result >= 0);
	return result;
}

// Truncate or extend file to length
// post(length() == 0);
void File_Stream::length(int64 length)
{
	int64 old_pos = pos();
	internal_seek(m_file, length, FILE_BEGIN);

	BOOL ok = SetEndOfFile(m_file);

	if (ok == 0)
		Throw_Win32_Exception("SetEndOfFile");

	// If file was truncated, seek to end
	if (length < old_pos)
		seek(length);
}

File_Stream::~File_Stream()
{
	CloseHandle(m_file);
}

//#include "common/assert.h"
//#include "common/Exception.h"
//#include "io/File_Stream.h"
//
//#include <cstdio>
//
//#ifdef _WIN32
//
//# include "win32/Throw_Win32_Exception.h"
//
//File_Stream::File_Stream(const char* filename)
//{
//	m_file = CreateFileA(
//		filename,
//		FILE_READ_DATA | FILE_WRITE_DATA,
//		FILE_SHARE_WRITE,
//		0,
//		OPEN_ALWAYS,
//		0,
//		0);
//
//	if (m_file == INVALID_HANDLE_VALUE)
//		Throw_Win32_Exception("CreateFileA");
//}
//
//// Read at most 'count' bytes into buf. Return number of actual bytes
//// read. Number of actual bytes can be less than 'count' if EOF is
//// encountered or if the stream has no data to be read. In case of
//// error, Exception is thrown.
//int File_Stream::Read(byte* buf, int count)
//{
//	ulong result;
//
//	int success = ReadFile(
//		m_file,
//		buf,
//		count,
//		&result,
//		0);
//
//	if (success == 0)
//	{
//		
//	    char szBuf[80];
//		char* lpMsgBuf;
//		DWORD dw = GetLastError(); 
//
//		FormatmessageA(
//			FORMAT_MESSAGE_ALLOCATE_BUFFER |
//			FORMAT_MESSAGE_FROM_SYSTEM,
//			NULL,
//			dw,
//			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
//			(char*) &lpMsgBuf,
//			0, NULL );
//
//		std::sprintf(szBuf, 
//			"%s failed with error %d: %s", 
//			"feh", dw, lpMsgBuf);
//
//		throw Exception(szBuf);
//
////		LocalFree(lpMsgBuf);
////		ExitProcess(dw); 
//	}
//
//	return result;
//}
//
//// Write 'count' bytes.
//// TBD: Actual semantics
//void File_Stream::Write(const byte* buf, int count)
//{
//}
//
//int File_Stream::Pos()
//{
//	uint result = SetFilePointer(m_file, 0, 0, FILE_CURRENT);
//	
//	return result;
//}
//
//int File_Stream::Length()
//{
//	uint result = GetFileSize(m_file, 0);
//
//	return result;
//}
//
//void File_Stream::Length(int value)
//{
//	//SetFileSize
//}
//
//File_Stream::~File_Stream()
//{
//	CloseHandle(m_file);
//}
//
//#else
//
//File_Stream::File_Stream(const char* filename)
//{
//	ASSERT_MSG(false, "Not implemented");
//}
//
//// Read at most 'count' bytes into buf. Return number of actual bytes
//// read. TBD: Actual semantics
//virtual int Read(byte* buf, int count)
//{
//}
//
//// Write 'count' bytes.
//// TBD: Actual semantics
//virtual void Write(const byte* buf, int count)
//{
//}
//
//int File_Stream::Pos()
//{
//	ASSERT_MSG(false, "Not implemented");
//	return 0;
//}
//
//int File_Stream::Length()
//{
//	ASSERT_MSG(false, "Not implemented");
//	return 0;
//}
//
//void File_Stream::Length(int value)
//{
//	ASSERT_MSG(false, "Not implemented");
//}
//
//#endif


